﻿#!/usr/bin/env
#-*- coding:utf-8 -*-
"""
 1.rs.sysfix.cn like this called base_domain allow import rs.sysfix.cn,rs.sysfix.cn.demo,etc..;rs.sysfix ... others error
 2.针对-和_进行了特殊的编码，-编码为__2,_编码为__3, 编码为__4,. 编码为__5,譬如:
import webimporter as m_web_importer
m_web_importer.__uranium_has_crypto__ = True
m_web_importer.register_base_domain("uranium238.oss-cn-beijing.aliyuncs.com", "http")

import uranium238.oss__2cn__2beijing.aliyuncs.com.boot as m_boot

m_web_importer.register_base_domain("u__2.uranome.com", "http")
import u__3__32.uranome.com.do__4not__4go__4away as m_do_not_go_away   # script name is "do not go away.py"
 3. ___的占位特殊编码（解码时会被做空处理），比如可以加在数字开头前面，或者某些特殊名字的任何部位
"""

import imp
import sys
import socket
import threading
import time
import importlib
import importlib.abc

import urllib
import urllib.error
import urllib.request
import urllib.parse
import bz2

import http
import http.client as hclient

from types import ModuleType

import logging
from logging import handlers

__author__ = 'shawhen'
__version__ = '2.4.8'
__date__ = '2015-02-03'

# log system
handler = handlers.RotatingFileHandler("webimporter.log", maxBytes=2*1024*1024)
fmt = "%(asctime)s - %(filename)s:%(lineno)s - %(name)s - %(levelname)s - %(threadName)s: %(message)s"
formatter = logging.Formatter(fmt)
handler.setFormatter(formatter)

general_logger = logging.getLogger("general")
general_logger.addHandler(handler)
general_logger.setLevel(logging.DEBUG)

__uranium_debug__ = True

import Crypto.Cipher.AES as AES
# def aes_cipher(key,mode = AES.MODE_CBC,iv = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16'):
def aes_cipher(key, mode = AES.MODE_CBC, iv = '1234567890123456'):
    '''
    allocate a aes cipher,automatic align key 256bit
    '''
    real_key = None
    if len(key) >= 16:
        real_key = key[0:16]
    else:
        need_align_bytes = 16-len(key)
        if isinstance(key,str):
            real_key = key + '\x00'*need_align_bytes
        elif isinstance(key,bytes):
            real_key = key + b'\x00'*need_align_bytes
    cipher = AES.new(real_key,mode,iv)
    return cipher

def aes_encrypt(origin, key, mode = AES.MODE_CBC, iv = '1234567890123456'):
    cipher = aes_cipher(key,mode,iv)
    #origin align
    tmp = len(origin) % AES.block_size
    if tmp != 0:
        missing_aligned_len = AES.block_size - tmp
        if isinstance(origin,str):
            aligned_origin = origin + '\x00'*missing_aligned_len
        elif isinstance(origin,bytes):
            aligned_origin = origin + b'\x00'*missing_aligned_len
        return cipher.encrypt(aligned_origin)
    else:
        return cipher.encrypt(origin)

def aes_decrypt(encrypted, key, mode = AES.MODE_CBC, iv = '1234567890123456'):
    cipher = aes_cipher(key,mode,iv)
    #encrypted align
    tmp = len(encrypted) % AES.block_size
    if tmp != 0:
        missing_aligned_len = AES.block_size - tmp
    else:
        missing_aligned_len = 0
    if missing_aligned_len != 0:
        if isinstance(encrypted,str):
            aligned_encrypted = encrypted + '\x00'*missing_aligned_len
        elif isinstance(encrypted, bytes):
            aligned_encrypted = encrypted + b'\x00'*missing_aligned_len
        return cipher.decrypt(aligned_encrypted)
    else:
        return cipher.decrypt(encrypted)

__uranium_has_crypto__ = False
__uranium_crypto_key__ = b""

class WebImporterLoader():
    __module_type_base_domain_section__ = 0  #section of base domain,cannot load
    __module_type_normal__ = 1  #normal,can load

    def __init__(self):
        pass

    @classmethod
    def module_repr(self, module):
        print("repr for module:", module.__name__)
        repr_str = "<module {0} from {1}>".format(module.__name__, module.__file__)
        print("str:", repr_str)
        return repr_str

    def get_filename(self, fullname):
        print("get file name for:", fullname)
        return "N/A"

    def get_source(self, fullname):
        return b""

    def create_module(self, spec):
        if __uranium_debug__:
            print("create module for spec:", spec.name)
        self.spec = spec
        if spec.name in sys.modules:
            return sys.modules[spec.name]
        importer_registered_domains = spec.importer._registered_domains
        for registered_domain in importer_registered_domains:
            if spec.name in registered_domain:
                return self
        # importer_registered_straps = spec.importer._straps_domain
        # for registered_strap, _ in importer_registered_straps.items():
        #     if spec.name in registered_strap:
        #         return self
        mod = imp.new_module(spec.name)
        mod.__loader__ = self
        mod._type = WebImporterLoader.__module_type_normal__
        sys.modules[spec.name] = mod

        return mod

    def exec_module(self, module):
        print("exec module v6 for:", module.__name__)

        if isinstance(module, ModuleType) is False:
            return
        if module._type == WebImporterLoader.__module_type_base_domain_section__:
            print('registered domain section module,return directly')
            return
        importer = module.__loader__.spec.importer
        importer_exec_module_lock = importer._exec_module_lock
        spec = module.__loader__.spec
        # 找出域名部分
        pkg_registered_domain = '' # 这个包所属的主机
        decoded_spec_name = spec.name.replace("__3", "_").replace("__2", "-").replace("__4", " ").replace("__5", ".").replace("___", "").replace("__com", "com") # 包名解码后的路径名，如: a__2b.c__2d -> a-b.c-d

        # 在所有注册域名中寻找是否有匹配的域名
        for registered_domain in importer._registered_domains:
            pos = decoded_spec_name.find(registered_domain)  # 如果包名包含这个注册域名，则说明这个包是在这个注册域名之下
            if pos == -1:
                continue
            else:
                pkg_registered_domain = registered_domain
        # if __uranium_debug__:
        #     print('host:', pkg_registered_domain)
        strap = None
        if len(pkg_registered_domain) <= 0:  # 这说明这个包要么是非注册域名，要么是strap
            if "." in pkg_registered_domain:  # 非注册域名
                raise ImportError("import from unregistered domain")
            else:  # strap
                for strap_i, strap_domain in importer._straps_domain.items():
                    if decoded_spec_name.startswith(strap_i):
                        strap = strap_i
                        pkg_registered_domain = strap_domain

                        break
                else:
                    raise ImportError("unknown strap")

        domain_context = importer._registered_domains.get(pkg_registered_domain)
        if domain_context is None:
            raise ValueError("domain({0}) context is None".format(pkg_registered_domain))
        protocol = domain_context.get("protocol", "http")
        domain_baseurl = domain_context.get("baseurls", {}).get(strap)
        domain_kwargs = domain_context.get("kwargs", {})
        domain_cipher = domain_kwargs.get("cipher")
        domain_type = domain_kwargs.get("type")
        domain_passwd = domain_kwargs.get("passwd")

        # 构造.分制包路径
        if strap is None:
            path = decoded_spec_name[len(pkg_registered_domain):]  # 除开主机之外，其余的全为路径(a.b.com.d.e, .d.e is path)
        else:
            path = decoded_spec_name  # (cburanome.uranome.gui.qt, cburanome.uranome.gui.qt is path)

        file_content = None
        # 构造对应协议的url（不包含后缀的）
        file_base_url = None
        if domain_baseurl is None:
            if protocol == "http":
                file_base_url = protocol+'://'+pkg_registered_domain+path.replace('.', '/')
        else:
            file_base_url = domain_baseurl+path.replace('.', '/')
        # 域名内包含zip的，可以找找看有没有父包zip
        if domain_type == "zip":
            for pkg_path, zip_context in importer._pkg_zip.items():
                if pkg_path in decoded_spec_name:
                    zip = zip_context["zip"]
                    try:
                        file_name_in_zip = decoded_spec_name.replace(pkg_path, "")
                        if file_name_in_zip.startswith("."):
                            file_name_in_zip = file_name_in_zip[1:]
                        file_name_in_zip = file_name_in_zip.replace(".", "/")
                        # 后缀
                        if domain_cipher is not None:
                            file_name_in_zip += ".pye"
                        else:
                            file_name_in_zip += ".py"
                        zip_file = zip.open(file_name_in_zip, "r", domain_passwd)
                        file_content = zip_file.read()

                        file_url = file_base_url+pkg_path+".zip/"+file_name_in_zip
                    except Exception as e:
                        pass
                    break
        if file_content is None:
            # 分配合适的后缀(们)
            suffixs = []
            # 如果这个域名被指明有zip/bz2...存在
            if domain_type is not None:
                suffixs.append("."+domain_type)
            if domain_kwargs.get("cipher") is not None:
                suffixs.append('.pye')
            else:
                suffixs.append(".py")

            file_url = None
            if pkg_registered_domain == decoded_spec_name:
                # 这就是一个注册域名，不用获取文件内容了
                file_content = ""
            else:
                if protocol == "http":
                    for suffix in suffixs:
                        try:
                            file_url = file_base_url+suffix
                            file_content = importer.request_file(domain_context, file_url)
                            #request directory's __init__.py
                            if file_content is None:
                                # print('base_url:', base_url)
                                file_url = file_base_url+'/__init__'+suffix
                                file_content = importer.request_file(domain_context, file_url)

                            if file_content is not None:
                                break
                        except Exception as e:
                            import traceback
                            traceback.print_exc()
                elif protocol == "nfs":
                    importer_exec_module_lock.acquire()

                    domain_nfs_context = importer._nfs_domain_context.get(pkg_registered_domain)
                    for i in range(0, 50):
                        try:
                            file_url = path.replace(".", "/")+suffix
                            if __uranium_debug__:
                                print("----------{0} to be read".format(file_url))
                            script_file = domain_nfs_context.open(file_url)
                            file_content = script_file.read()
                            if __uranium_debug__:
                                print("----------{0} read".format(file_url))
                            break
                        except FileNotFoundError as e:  # 还可以被当做目录访问其下的__init__
                            if __uranium_debug__:
                                print("******{0} not found*******".format(file_url))
                            file_url = path.replace(".", "/")+"/__init__"+suffix
                            for i in range(0, 50):
                                try:
                                    if __uranium_debug__:
                                        print("----------{0} to be read".format(file_url))
                                    script_file = domain_nfs_context.open(file_url)
                                    file_content = script_file.read()
                                    if __uranium_debug__:
                                        print("----------{0} read".format(file_url))
                                    break
                                except FileNotFoundError as e:
                                    if __uranium_debug__:
                                        print("**********{0} not found***********".format(file_url))
                                    break
                                except ValueError as e:
                                    import traceback
                                    exc_text = traceback.format_exc()
                                    traceback.print_exc()
                                    general_logger.debug(exc_text)
                                    if "unknow error" in str(e):
                                        continue
                                    elif "network broken" in str(e):
                                        time.sleep(2)
                                        continue
                            break
                        except ValueError as e:
                            import traceback
                            exc_text = traceback.format_exc()
                            traceback.print_exc()
                            general_logger.debug(exc_text)
                            if "unknow error" in str(e):
                                continue
                            elif "network broken" in str(e):
                                time.sleep(2)
                                continue

                    importer_exec_module_lock.release()
        if file_content is None:
            raise ImportError("module({0})'s file content is None".format(decoded_spec_name))
        if len(file_content) == 0:
            return

        if file_url.endswith("zip"):
            from io import BytesIO
            from zipfile import ZipFile

            file_like_content = BytesIO(file_content)
            file_zip = ZipFile(file_like_content)
            importer._pkg_zip[decoded_spec_name] = {"zip": file_zip}
        else:
            plain_file_content = ''
            if domain_cipher is not None and file_url.endswith(".py") is False:
                #解密
                file_content = aes_decrypt(file_content, domain_cipher)
                # if __uranium_debug__:
                #     print('decrypted_file_content(not decode):',decrypted_file_content)
            compress_method = domain_kwargs.get("compress")
            if compress_method is not None and file_url.endswith(".py") is False:
                #解压缩
                if compress_method == "bz2":
                    file_content = bz2.decompress(file_content)
                    file_content = file_content.decode()
                    # if __uranium_debug__:
                    #     print('decompressed file content:',decoded_file_content)
                    if len(file_content) <= 0:
                        return
                    cursor = len(file_content) - 1
                    while file_content[cursor] == '\x00' and cursor >= 0:
                        cursor -= 1
                    file_content = file_content[:cursor+1]
                else:
                    raise ValueError("unsupported compress method: {0}".format(compress_method))

            # 有__path__属性就被认为是一个package，但我们不能让它这么做
            if file_url.endswith("__init__.pye") or file_url.endswith("__init__.py"):
                pass
            else:
                try:
                    delattr(module, "__path__")
                except AttributeError as e:
                    pass

            module.__file__ = file_url
            self.name = decoded_spec_name
            self.path = file_url
            exec(file_content, module.__dict__)


class WebImporterSpec(object):
    def __init__(self, spec_name, import_path, importer, target_module):
        self.loader = WebImporterLoader()
        self.origin = ""
        self.name = spec_name
        self.importer = importer
        self.submodule_search_locations = []
        self.has_location = True
        self.cached = False


class WebImporter(object):
    def __init__(self):
        self.specs = {}
        self._registered_domains = {}  # registered domains to protocol
        self.domain_modules = []
        self._straps_domain = {}  # 简称到域名的映射
        self._pkg_zip = {}  # 包（从这开始，之后这个包下面的子包优先从zip中获取）对应的zip a.b.c.d -> {"zip": xx}
        self._exec_module_lock = threading.Lock()

    def find_spec(self, pkg_name, import_path, target_module):
        if __uranium_debug__:
            print('(pyfly v6)to find ', pkg_name, 'spec', "import path:", import_path)
        can_handle = True

        decoded_pkg_name = pkg_name.replace("__3", "_").replace("__2", "-").replace("__4", " ").replace("__5", ".").replace("___", "").replace("__com", "com")

        # 域名查找
        pkg_name_parts = decoded_pkg_name.split('.')
        if len(self._registered_domains) == 0:
            can_handle = False
        else:
            for registered_domain in self._registered_domains:
                registered_domain_parts = registered_domain.split('.')
                need_compare_parts = min(len(pkg_name_parts), len(registered_domain_parts))
                for i in range(need_compare_parts):
                    if pkg_name_parts[i] != registered_domain_parts[i]:
                        can_handle = False
                        break
                else:
                    can_handle = True
                    break

        # 根据域名全称如果没找到的话，试着到strap里面找找
        if can_handle is False:
            for strap in tuple(self._straps_domain.keys()):
                if decoded_pkg_name.startswith(strap):
                    can_handle = True
                    break

        if not can_handle:
            print("(pyfly v6)we cann't handle this", pkg_name)
            return None
        else:
            print("(pyfly v6)we can handle this", pkg_name)
            pass

        result = self.specs.get(pkg_name)
        if result is None:
            spec = WebImporterSpec(pkg_name, import_path, self, target_module)
            self.specs[pkg_name] = spec
            return spec
        print("(pyfly)we have already this spec,name", decoded_pkg_name)
        return result

    @staticmethod
    def request_file(domain_contxt, url, chunk_cb = None):
        """
        :param chunk_cb: proto(this time chunk, all downloaded chunk, toal size)
        返回文件原始内容, 访问成功，但不是状态不是200，返回None
        """
        print("(pyfly v6)request file:", url)

        if url is None:
            return

        http_conn = domain_contxt.get("conn")
        domain = domain_contxt.get("domain")
        if domain is None:
            if url.startswith("http"):
                domain = urllib.parse.urlparse(url).hostname
            else:
                domain = urllib.parse.urlparse("http://"+url).hostname
        domain_kwargs = domain_contxt.get("kwargs", {})
        domain_port = domain_kwargs.get("port", 80)
        domain_header = domain_contxt.get("header", {})

        for i in range(0, 200):
            try:
                if http_conn is None:
                    for i in range(0, 200):
                        try:
                            http_conn = hclient.HTTPConnection(domain, domain_port)
                            domain_contxt["conn"] = http_conn

                            break
                        except Exception as e:
                            if "getaddrinfo failed" in str(e) or "timed out" in str(e):
                                time.sleep(1)

                    continue
                http_conn.request("GET", url, headers=domain_header)
                url_response = http_conn.getresponse()
                if url_response.status != 200:
                    return None
                file_content = url_response.read()

                if chunk_cb is not None:
                    chunk_cb(file_content, file_content, len(file_content))

                return file_content

                break
            except http.client.ResponseNotReady as e:
                pass
            except Exception as e:
                import traceback
                traceback.print_exc()
            finally:
                for i in range(0, 200):
                    try:
                        http_conn = hclient.HTTPConnection(domain, domain_port)
                        domain_contxt["conn"] = http_conn

                        break
                    except Exception as e:
                        if "getaddrinfo failed" in str(e) or "timed out" in str(e):
                            time.sleep(1)

print("*****************pyfly v6 hot fix******************")
__web_importer__ = WebImporter()
#替换由c内置的webimporter
for old_importer_mod in sys.meta_path:
    if hasattr(old_importer_mod, "__web_importer__"):
        print("*************replace c web importer to py web importer*****************")
        old_importer_mod.register_base_domain = register_base_domain

        old_importer = old_importer_mod.__web_importer__

        old_importer.find_spec = __web_importer__.find_spec

        __web_importer__.specs = old_importer.specs
        __web_importer__._registered_domains = old_importer._registered_domains
        __web_importer__.domain_modules = importer.domain_modules

        #之前由c webimporter导入的终端包(boot),当然还有就是这个包自己
        for module_name, module in sys.modules.items():
            if module_name.endswith("boot") or module_name == __name__:
                print("delete __path__ for {0}".format(module_name))
                try:
                    delattr(module, "__path__")
                except Exception as e:
                    pass
else:
    sys.meta_path.append(__web_importer__)
for module_path, module in sys.modules.items():
    if module.__name__ == "__main__":
        if hasattr(module, "__uranium_crypto_key__"):
            if __uranium_debug__:
                print("got main's uranium crypto key")
            __uranium_crypto_key__ = module.__uranium_crypto_key__
        if hasattr(module, "__uranium_has_crypto__"):
            if __uranium_debug__:
                print("got main's crypto prop")
            __uranium_has_crypto__ = module.__uranium_has_crypto__
        if hasattr(module, "__uranium_debug__"):
            __uranium_debug__ = module.__uranium_debug__

import platform
import hashlib
platform_text = platform.platform()

g__libnfs_dll = None

def register_base_domain(domain, protocol, *args, **kwargs):
    """
    :param kwargs cipher: aes 256(aka. 32-bytes) key
                  compress: bz2
                  strap: ...
                  baseurl: ...
                  type: zip/...
                  passwd: ...
    """
    global __web_importer__
    global g__libnfs_dll

    print("register {0} with {1}, args: {2}, kwargs: {3}".format(domain, protocol, args, kwargs))

    if "baseurl" in kwargs:
        if kwargs["baseurl"].endswith("/"):
            pass
        else:
            kwargs["baseurl"] += "/"

    if protocol == "nfs":  # nfs协议需要一个libnfs的组件和一个上下文
        print("supported protocol: nfs")
        # nfs的domain可能是一个a.b.c.d/code/hello这样的一个url，我们把/换成.,将其全部注册入域名集
        __web_importer__._registered_domains[domain.replace("/", ".")] = protocol
        # 准备组件
        if g__libnfs_dll is None:
            if "Win" in platform_text:  # Windows
                need_download = False
                try:
                    win_libnfs_digest = "f0d45263f5b3ad8b2f0b588a18ff150e"
                    local_libnfs_content = open("libnfs.dll", "rb").read()
                    md5er = hashlib.md5()
                    md5er.update(local_libnfs_content)
                    local_libnfs_digest = md5er.hexdigest()
                    if local_libnfs_digest != win_libnfs_digest:
                        need_download = True
                except FileNotFoundError as e:
                    need_download = True
                if need_download is True:
                    from urllib.request import urlretrieve
                    while True:
                        try:
                            urlretrieve("http://uranome.oss-cn-beijing.aliyuncs.com/component/nfs/latest/win32/libnfs.dll", "libnfs.dll")
                            break
                        except (socket.gaierror, urllib.error.URLError) as e:
                            if "getaddrinfo failed" in str(e):
                                time.sleep(1)
                                continue
                        except socket.timeout as e:
                            continue
                g__libnfs_dll = ctypes.cdll.libnfs
            else:
                raise NotImplementedError
        # 为要注册的domain初始化上下文
        if hasattr(__web_importer__, "_nfs_domain_context") is False:
            __web_importer__._nfs_domain_context = {}
        __web_importer__._nfs_domain_context[domain.replace("/", ".")] = NFS("nfs://"+domain)

        parts = domain.replace("/", ".").split('.')
        whole = []
        for part in parts:
            whole.append(part)
            spec_name = '.'.join(whole)
            __web_importer__.domain_modules.append(spec_name)
            spec = WebImporterSpec(spec_name, None, __web_importer__, None)
            __web_importer__.specs[spec_name] = spec
    else:
        domain_context = __web_importer__._registered_domains.get(domain)
        if domain_context is not None and isinstance(domain_context, dict):  # use this version of pyfly registered
            pass
        else:
            for i in range(0, 200):
                try:
                    domain_port = kwargs.get("port", 80)
                    http_conn = hclient.HTTPConnection(domain, domain_port)
                    break
                except Exception as e:
                    if "getaddrinfo failed" in str(e) or "timed out" in str(e):
                        time.sleep(1)
            else:
                raise ValueError("register domain:{0} with protocol:{1} failed".format(domain, protocol))
            domain_context = {"domain": domain, "protocol": protocol, "kwargs": kwargs, "conn": http_conn,
                              "header": {"Connection":"Keep-Alive",},}
            __web_importer__._registered_domains[domain] = domain_context

        domain_strap = kwargs.get("strap")
        if domain_strap is not None:
            __web_importer__._straps_domain[domain_strap] = domain

            strap_spec = WebImporterSpec(domain_strap, None, __web_importer__, None)
            __web_importer__.specs[domain_strap] = strap_spec

            if "baseurl" in kwargs:
                if "baseurls" not in domain_context:
                    domain_context["baseurls"] = {}
                domain_context["baseurls"][domain_strap] = kwargs["baseurl"]
        # 域名部分的spec
        parts = domain.split('.')
        whole = []
        for part in parts:
            whole.append(part)
            spec_name = '.'.join(whole)
            __web_importer__.domain_modules.append(spec_name)
            spec = WebImporterSpec(spec_name, None, __web_importer__, None)
            __web_importer__.specs[spec_name] = spec

def enable_crypto(enable, key = None):
    global __uranium_has_crypto__
    global __uranium_crypto_key__
    __uranium_has_crypto__ = enable
    if enable is True and key is not None:
        if len(key) != 32:
            key += "\x00"*32-len(key)
        __uranium_crypto_key__ = key



# nfs 实现
import ctypes
import os
import threading

time_t = ctypes.c_longlong

class nfs_url(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ("server", ctypes.c_char_p),
        ("path", ctypes.c_char_p),
        ("file", ctypes.c_char_p)
    ]

class nfs_fh3(ctypes.Structure):
    _pack_ = 1
    class Sdata(ctypes.Structure):
        _pack_ = 1
        _fields_ = [
            ("data_len", ctypes.c_uint),
            ("data_val", ctypes.c_char_p)
        ]
    _fields_ = [
        ("data", Sdata)
    ]

class nfs_readahead(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ("fh_offset", ctypes.c_uint64),
        ("last_offset", ctypes.c_uint64),
        ("buf_offset", ctypes.c_uint64),
        ("buf_count", ctypes.c_uint64),
        ("buf_ts", time_t),
        ("buf", ctypes.c_void_p),
        ("cur_ra", ctypes.c_uint32)
    ]

class nfsfh(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ("fh", nfs_fh3),
        ("is_sync", ctypes.c_int),
        ("is_append", ctypes.c_int),
        ("offset", ctypes.c_uint64),
        ("ra", nfs_readahead)
    ]

class nfs_stat_64(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ("nfs_dev", ctypes.c_uint64),
        ("nfs_ino", ctypes.c_uint64),
        ("nfs_mode", ctypes.c_uint64),
        ("nfs_nlink", ctypes.c_uint64),
        ("nfs_uid", ctypes.c_uint64),
        ("nfs_gid", ctypes.c_uint64),
        ("nfs_rdev", ctypes.c_uint64),
        ("nfs_size", ctypes.c_uint64),
        ("nfs_blksize", ctypes.c_uint64),
        ("nfs_blocks", ctypes.c_uint64),
        ("nfs_atime", ctypes.c_uint64),
        ("nfs_mtime", ctypes.c_uint64),
        ("nfs_ctime", ctypes.c_uint64),
        ("nfs_atime_nsec", ctypes.c_uint64),
        ("nfs_mtime_nsec", ctypes.c_uint64),
        ("nfs_ctime_nsec", ctypes.c_uint64),
        ("nfs_used", ctypes.c_uint64)
    ]

class NFSFH(object):
    def __init__(self, nfs_context, path, mode='r'):
        self._nfs_context = nfs_context
        self._name = path

        if path[:6] == "nfs://":
            _pos = path.rfind('/')
            _dir = path[:_pos]
            path = path[_pos:]
            self._nfs_context = NFS(_dir)
        self._nfs_context_p = self._nfs_context._nfs_context_p
        self._nfs_context_io_lock = self._nfs_context._io_lock

        _plus = True if '+' in mode else False
        _mode = 0
        if 'r' in mode:
            _mode = os.O_RDWR if _plus else os.O_RDONLY
        if 'w' in mode:
            _mode = os.O_RDWR if _plus else os.O_WRONLY
            _mode |= os.O_CREAT|os.O_TRUNC
        if 'a' in mode:
            _mode = os.O_RDWR if _plus else os.O_WRONLY
            _mode |= os.O_CREAT|os.O_APPEND

        self._nfsfh_p = ctypes.POINTER(nfsfh)()
        if _mode & os.O_CREAT:
            self._nfs_context_io_lock.acquire()

            open_code = g__libnfs_dll.nfs_create(self._nfs_context_p, path.encode(), _mode, 0o0664, ctypes.byref(self._nfsfh_p))

            self._nfs_context_io_lock.release()
        else:
            self._nfs_context_io_lock.acquire()

            open_code = g__libnfs_dll.nfs_open(self._nfs_context_p, path.encode(), _mode, ctypes.byref(self._nfsfh_p))

            self._nfs_context_io_lock.release()

            if open_code == 0:
                pass
            elif open_code == -2: # file not found
                raise FileNotFoundError("nfs://"+self._nfs_context._url_p.contents.server.decode()+self._nfs_context._url_p.contents.path.decode()+path)
            elif open_code == -14:  # 打开目录
                raise ValueError("api error, use open dir")
            elif open_code == -13:  # 权限不够
                raise ValueError("remote file permission denied")
            elif open_code == -5:  # 暂时碰到的好像是网络断开之类的
                raise ValueError("network broken")
            else:  # 暂不知道的一个错误。。
                raise ValueError("unknow error,ec: {0}".format(open_code))
        # self._nfsfh = NFSFileHandle_value(self._nfsfh)
        self._closed = False
        self._need_flush = False
        self._writing = True if _mode & (os.O_RDWR|os.O_WRONLY) else False

    def __del__(self):
        pass

    def close(self):
        self._nfs_context_io_lock.acquire()

        if self._need_flush:
            self.flush()
        g__libnfs_dll.nfs_close(self._nfs_context_p, self._nfsfh_p)
        self._closed = True

        self._nfs_context_io_lock.release()

    def write(self, data):
        if self._closed:
            raise ValueError('I/O operation on closed file')
        if not self._writing:
            raise IOError('Trying to write on file open for reading')

        self._nfs_context_io_lock.acquire()

        if isinstance(data, bytes):
            g__libnfs_dll.nfs_write(self._nfs_context_p, self._nfsfh_p, len(data), data)
        elif isinstance(data, str):
            data_bytes = data.encode()
            g__libnfs_dll.nfs_write(self._nfs_context_p, self._nfsfh_p, len(data_bytes), data_bytes)
        self._need_flush = True

        self._nfs_context_io_lock.release()

    def read(self, size=-1):
        if size < 0:
            _pos = self.tell()

            _st = nfs_stat_64()
            g__libnfs_dll.nfs_fstat64(self._nfs_context_p, self._nfsfh_p, ctypes.byref(_st))

            size = _st.nfs_size - _pos

        buf = ctypes.create_string_buffer(size)

        self._nfs_context_io_lock.acquire()

        count = g__libnfs_dll.nfs_read(self._nfs_context_p, self._nfsfh_p, ctypes.c_uint64(size), buf)

        self._nfs_context_io_lock.release()
        return buf.raw[:count]

    def tell(self):
        _pos = ctypes.c_uint64(0)

        self._nfs_context_io_lock.acquire()

        g__libnfs_dll.nfs_lseek(self._nfs_context_p, self._nfsfh_p,
                             ctypes.c_int64(0), os.SEEK_CUR,
                             ctypes.byref(_pos))

        self._nfs_context_io_lock.release()
        return _pos.value

    def seek(self, offset, whence=os.SEEK_CUR):
        _pos = 0

        self._nfs_context_io_lock.acquire()

        g__libnfs_dll.nfs_lseek(self._nfs_context_p, self._nfsfh_p, offset, whence, ctypes.byref(_pos))

        self._nfs_context_io_lock.release()
        return _pos

    def truncate(self, offset=-1):
        if offset < 0:
            offset = self.tell()

        self._nfs_context_io_lock.acquire()

        g__libnfs_dll.nfs_ftruncate(self._nfs_context_p, self._nfsfh_p, offset)

        self._nfs_context_io_lock.release()

    def fileno(self):
        _st = nfs_stat_64()

        self._nfs_context_io_lock.acquire()

        g__libnfs_dll.nfs_fstat64(self._nfs_context_p, self._nfsfh_p, ctypes.byref(_st))

        self._nfs_context_io_lock.release()
        return _st.nfs_ino

    def flush(self):
        if self._closed:
            raise ValueError('I/O operation on closed file')

        self._nfs_context_io_lock.acquire()

        g__libnfs_dll.nfs_fsync(self._nfs_context_p, self._nfsfh_p)

        self._nfs_context_io_lock.release()

        self._need_flush = False

    def isatty(self):
        return False

    @property
    def name(self):
        return self._name

    @property
    def closed(self):
        return self._closed


class NFS(object):
    def __init__(self, url):
        self._io_lock = threading.Lock()
        self._nfs_context_p = g__libnfs_dll.nfs_init_context()
        self._url_p = ctypes.cast(g__libnfs_dll.nfs_parse_url_dir(self._nfs_context_p, url.encode()),
                                  ctypes.POINTER(nfs_url))
        g__libnfs_dll.nfs_mount(self._nfs_context_p, self._url_p.contents.server, self._url_p.contents.path)

    def __del__(self):
        g__libnfs_dll.nfs_destroy_url(self._url_p)
        g__libnfs_dll.nfs_destroy_context(self._nfs_context_p)

    def open(self, path, mode='rb'):
        nfsfh = NFSFH(self, path, mode=mode)

        return nfsfh